﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SharpShare.Storage;
using SharpShare.Storage.Searching;
using System.Collections.Concurrent;
using SharpShare.Storage.Searching.Parameters;

namespace SharpShare.Afp.Protocol.Handlers {
    internal class AfpCatSearchExRequestHandler : IAfpRequestHandler {
        public byte CommandCode {
            get { return 67; }
        }

        public AfpResultCode Process(IAfpSession session, DsiHeader dsiHeader, AfpStream requestStream, AfpStream responseStream) {
            requestStream.ReadPadding();

            ushort volumeId = requestStream.ReadUInt16();
            IAfpVolume volume = session.GetVolume(volumeId);

            if (volume == null) {
                return AfpResultCode.FPParamErr;
            }

            int maxMatches = requestStream.ReadInt32();
            requestStream.ReadUInt32(); // Reserved

            maxMatches = Math.Min(maxMatches, 10);

            Guid catalogPosition = new Guid(requestStream.ReadBytes(16));

            AfpFileDirectoryBitmap fileResultBitmap = requestStream.ReadEnum<AfpFileDirectoryBitmap>();
            AfpFileDirectoryBitmap directoryResultBitmap = requestStream.ReadEnum<AfpFileDirectoryBitmap>();

            AfpFileDirectoryBitmap reqBitmap1 = requestStream.ReadEnum<AfpFileDirectoryBitmap>();
            AfpFileDirectoryBitmap reqBitmap2 = requestStream.ReadEnum<AfpFileDirectoryBitmap>();

            AfpFileDirectoryBitmap partialNamesBit = (AfpFileDirectoryBitmap)0x8000;
            bool matchPartialNames = reqBitmap1.HasFlag(partialNamesBit);
            if (matchPartialNames) {
                reqBitmap1 ^= partialNamesBit;
            }

            ushort spec1Size = requestStream.ReadUInt16();
            AfpStream spec1Stream = new AfpStream(requestStream.ReadBytes(spec1Size));

            ushort spec2Size = requestStream.ReadUInt16();
            AfpStream spec2Stream = new AfpStream(requestStream.ReadBytes(spec2Size));

            AfpFileDirectoryBitmap searchBitmap = (reqBitmap1 | reqBitmap2);

            IList<SearchParameter> searchParams = volume.ReadSearchParameters(
                fileResultBitmap, directoryResultBitmap, searchBitmap, matchPartialNames, spec1Stream, spec2Stream)
                .ToList();

            CatSearchResultsContext context = GetResultsContext(session, volume.StorageProvider, searchParams, catalogPosition);

            if (context == null) {
                return AfpResultCode.FPCatalogChanged;
            }

            var results = context.PickUp().Take(maxMatches).ToList();

            if (results.Count == 0) {
                return AfpResultCode.FPEOFErr;
            }

            responseStream.WriteBytes(context.PositionToken.ToByteArray());

            AfpResultCode resultCode = results.WriteStorageItems(true, volume, fileResultBitmap, directoryResultBitmap, responseStream);

            return resultCode;
        }

        private static CatSearchResultsContext GetResultsContext(IAfpSession session, IStorageProvider container, IEnumerable<SearchParameter> parameters, Guid positionToken) {
            var contexts = session.Properties.GetOrSet<AfpCache<Guid, CatSearchResultsContext>>();

            if (positionToken != Guid.Empty) {
                return contexts[positionToken];
            }

            positionToken = Guid.NewGuid();
            IStorageSearchEngine searchEngine = container.SearchEngine;

            if (searchEngine == null) {
                searchEngine = new TreeStorageSearchEngine(container);
            }

            SearchParameter parameter = null;

            foreach (SearchParameter p in parameters) {
                if (parameter == null) {
                    parameter = p;
                } else {
                    parameter = parameter.And(p);
                }
            }

            var results = searchEngine.Search(parameter);

            CatSearchResultsContext context = new CatSearchResultsContext(positionToken, results);

            contexts.Add(positionToken, context, TimeSpan.FromMinutes(5.0));

            return context;
        }

        private sealed class CatSearchResultsContext {
            private IEnumerator<IStorageItem> _resultsEnumerator;

            public CatSearchResultsContext(Guid positionToken, IEnumerable<IStorageItem> results) {
                this.PositionToken = positionToken;
                _resultsEnumerator = results.GetEnumerator();
            }

            public Guid PositionToken { get; private set; }

            public DateTime DateLastUsed { get; private set; }

            public IEnumerable<IStorageItem> PickUp() {
                while (_resultsEnumerator.MoveNext()) {
                    this.DateLastUsed = DateTime.Now;
                    yield return _resultsEnumerator.Current;
                }
            }
        }
    }
}
